Uploading to FlashAir

Latest update: December 2013

In this tutorial, we will show you how to upload a file to your FlashAir with upload.cgi.
This tutorial builds off of Android Tutorial 4: Displaying Image Thumbnails.

Overview

We're going to sort images on your FlashAir by title, and by date.

Warning: If this operation is used incorrectly, the FlashAir file system may become corrupted and you may lose your data. Host devices, like a PC, can cache the contents of the SD card (FAT), but will not see changes made by upload.cgi. Therefore, a FAT file system conflict may occur if changes are made by the host device and upload.cgi at the same time. After using upload.cgi, re-insert the SD card and the host device will be able to see your changes.

We will add a Date List button to the content list.

This image shows the content list

A list, sorted by date, will be displayed when you tap the button.

This image shows an updated content list

Information will be displayed when you tap an item.

This image shows the new image in an image view

We will need to create the following files in order to write this application:

  • MainActivity.java
  • activity_main.xml
  • ImageViewActivity.java
  • activity_image_view.xml
  • DateListActivity.java
  • activity_date_list.xml
  • list_view_item.xml
  • MemoEditActivity.java
  • activity_memo_edit.xml
  • grid_view_item.xml
  • FlashAirRequest.java

Important: Please note that your project contains a file called AndroidManifest.xml. This file is used to manage application permissions. By default, applications are not permitted to access the internet. The path to this file should look something like: [Project_Folder]/AndroidManifest.xml
You will need to add the following line of code into your AndroidManifest.xml in order for this application to work:

<uses-permission android:name="android.permission.INTERNET" />

Editing strings.xml

Setup strings.xml as follows!
The path to this file should look something like: [Project_Folder]/res/values/strings.xml

<string name="app_name">android_tutorial_07</string>
<string name="action_settings">Settings</string>
<string name="title_activity_image_view">ImageViewActivity</string>
<string name="date_list">Date List</string>
<string name="back">Back</string>
<string name="directory_name">Directory Name</string>
<string name="date">Date</string>
<string name="title">Title</string>
<string name="title_">Title:</string>
<string name="memo">Memo</string>
<string name="memo_">Memo:</string>    
<string name="save">Save</string>
<string name="grid_view_image">grid_view_image</string>

Creating the List Layout

Lets start with activity_main.xml, which determines the layout of our Android App.
This can be found in your layout folder.
The path to this file should look something like: [Project_Folder]/res/layout/activity_main.xml

This file will be identical to the activity_main.xml file from Android Tutorial 3: Downloading Content, except with a button added to it.

Add the following lines to to activity_main.xml.

activity_main.xml

<Button
    android:id="@+id/button2"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:cacheColorHint="#00000000"
    android:text="@string/date_list"
    android:textColor="@android:color/white"
    android:textSize="20sp" />

Creating the Image Viewing Layout

Next, edit activity_image_view.xml. This file determines the layout of our image viewing screen.
This can also be found in your layout folder.
The path to this file should look something like: [Project_Folder]/res/layout/activity_image_view.xml

This file will be identical to the activity_main.xml file from Android Tutorial 3: Downloading Content.
Please refer to that tutorial for an explanation of the implementation.

Creating the Date List Layout

Next, write the activity_date_list.xml and list_view_item.xml files. They will determine the layout of our date list and the layout for the rows in the list.
They can be found in your layout folder.
The path to these files should look something like:
[Project_Folder]/res/layout/activity_date_list.xml
[Project_Folder]/res/layout/list_view_item.xml

You'll want the activity_date_list.xml file to look like this:

activity_date_list.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".DateListActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="#00000000"
        android:text="@string/back"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:scaleType="centerInside"
        android:text="@string/directory_name"
        android:textAlignment="center"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <ListView
        android:id="@+id/listView1"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:cacheColorHint="#00000000"
        android:clickable="true"
        android:headerDividersEnabled="false" />

</LinearLayout>

And you'll want the list_view_item.xml file to look like this:

list_view_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">     
   <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/date" />

   <TextView
       android:id="@+id/textView2"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/title" />
</LinearLayout>

Creating the Operation Screen Layout

Next, we will write the activity_memo_edit.xml and grid_view_item.xml files.
activity_memo_edit.xml determines the layout of our operation screen.
grid_view_item.xml is for displaying the thumbnails.
They can be found in your layout folder.
The path to this file should look something like:
[Project_Folder]/res/layout/activity_memo_edit.xml
[Project_Folder]/res/layout/grid_view_item.xml

activity_memo_edit.xml should look something like this:

activity_memo_edit.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".MemoEditActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:cacheColorHint="#00000000"
        android:text="@string/back"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:scaleType="centerInside"
        android:text="@string/directory_name"
        android:textAlignment="center"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="152dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:gravity="center"
        android:hint="@string/date"
        android:paddingLeft="10dp"
        android:textSize="18sp" />

    <GridView
        android:id="@+id/gridView1"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="0.01"
        android:horizontalSpacing="10dp"
        android:numColumns="5"
        android:verticalSpacing="10dp" >
    </GridView>

    <TextView
        android:id="@+id/TextView01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"        
        android:layout_marginTop="12dp"
        android:paddingLeft="10dp"
        android:text="@string/title_"
        android:textSize="18sp" />

    <EditText
        android:id="@+id/editTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="@string/title"
        android:inputType="text"
        android:textStyle="italic" >
    </EditText>

    <TextView
        android:id="@+id/TextView02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"        
        android:layout_marginTop="20dp"
        android:paddingLeft="10dp"
        android:text="@string/memo_"
        android:textSize="18sp" />

    <EditText
        android:id="@+id/editMemo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="@string/memo"
        android:inputType="text"
        android:textStyle="italic" />

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:cacheColorHint="#00000000"
        android:text="@string/save"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

</LinearLayout>

And grid_view_item.xml should look like this:

grid_view_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">   

    <ImageView  
        android:id="@+id/imageView1"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:contentDescription="@string/grid_view_image"        
       />  
</LinearLayout>

Creating the Content List

Next, we will modify MainActivity.java.
We will add the click behavior of the Date List button to Android Tutorial 4: Displaying Image Thumbnails.
And then we'll add a sort condition so we get only image files.

Initialization

Please take MainActivity.java from Android Tutorial 4: Displaying Image Thumbnails and copy it into your new MainActivity.java file.

We will add member variables and replace the onCreate(Bundle savedInstanceState) function with the code below:

MainActivity.java (1)

public class MainActivity extends Activity implements AdapterView.OnItemClickListener {

    ListView listView;
    ImageView imageView;
    TextView currentDirText;
    TextView numFilesText;
    Button backButton;
    Button datelistButton;
    String rootDir = "DCIM";
    String directoryName = rootDir; // Initialize to rootDirectory
    SimpleAdapter listAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            // Set buttons
            backButton = (Button)findViewById(R.id.button1);
            getWindow().setTitleColor(Color.rgb(65, 183, 216));
            backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), 
                                                        PorterDuff.Mode.SRC_IN);
            backButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(directoryName.equals(rootDir)) {
                        listRootDirectory();
                    }
                    else {
                        int index = directoryName.lastIndexOf("/");
                        directoryName = directoryName.substring(0, index);
                        listDirectory(directoryName);
                    }
                }
            });
            backButton.setEnabled(false); // Disable in root directory

            datelistButton = (Button)findViewById(R.id.button2);
            getWindow().setTitleColor(Color.rgb(65, 183, 216));
            datelistButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), 
                                                            PorterDuff.Mode.SRC_IN);
            datelistButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                     Intent dateList = new Intent(getBaseContext(), DateListActivity.class);     
                     dateList.putExtra("dir", currentDirText.getText());
                     MainActivity.this.startActivity(dateList);        
                }
            });

            listRootDirectory();
        }
        catch(Exception e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        }
    }

We added a Date List button to our previous MainActivity class.

  • Lines 38-49:
    We will set a Date List button behavior to press to go to the date list screen.

Sorting the object

Add an if statement to listDirectory() so that we only see image files.

MainActivity.java (2)

            if( (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpg")) 
                || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg"))
                || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpe")) 
                || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".png")) ) {
                // Image file
                fileNames.add(allFiles[i]);
            }

Setting Up Restart and Back Button Handling

We want the list to be updated when the screen is restarted (for example when the user hits the back button).

MainActivity.java (3)

    @Override
    public void onRestart(){
        super.onRestart();
        listDirectory(directoryName);
    }

Creating the Image Viewing Screen

class ImageViewActivity will be identical to the version in Android Tutorial 3: Downloading Content. Please refer to that tutorial for an explanation of the implementation.

Creating the Date List Screen

Initialization

We will start by changing the class declaration, since we want to include a list that contains clickable items.

#### *DateListActivity.java*   (1)

lined: public class DateListActivity extends Activity implements AdapterView.OnItemClickListener {

Then we'll declare the views, other class variables, and organize the screens format.
We will also override the onCreate(Bundle savedInstanceState) function that initializes the Activity class.
We want the initialization function to set up the list as well as to create a click listener for the Button that we will use to return to the parent directory.

DateListActivity.java (2)

    ListView listView;
    ImageView imageView;
    TextView currentDirText;
    Button backButton;
    String rootDir = "DCIM";
    String directoryName;
    SimpleAdapter listAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_date_list);
        try {
            Bundle extrasData = getIntent().getExtras();
            directoryName = extrasData.getString("dir");
            if(!directoryName.equals(rootDir)) {
                int index = directoryName.lastIndexOf("/");
                directoryName = directoryName.substring(0, index);
            }    

            // Set buttons
            backButton = (Button)findViewById(R.id.button1);
            getWindow().setTitleColor(Color.rgb(65, 183, 216));
            backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), 
                                                        PorterDuff.Mode.SRC_IN);
            backButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    DateListActivity.this.finish(); // Go back to Get screen
                }
            });            
            listDirectory(directoryName);
        }
        catch(Exception e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        }
    }
  • Lines 22-31:
    We set the Button that will allow the user to return to the parent directory.

Creating the List

We just need to get the content list with command.cgi, and then get the file's timestamps as well.
This is to avoid problems when two files have the same name.

DateListActivity.java (3)

    public void listRootDirectory() {
        directoryName = rootDir;
        listDirectory(directoryName);
    }

    public void listDirectory(String dir) {
        // Prepare command directory path
        currentDirText = (TextView)findViewById(R.id.textView1);
        currentDirText.setText(dir + "/");

        // Fetch list of items in directory and display in a ListView
        new AsyncTask<String, Void, ListAdapter>(){
            @Override
            protected ListAdapter doInBackground(String... params) {
                String dir = params[0];

                String cmddir = "/" + dir;
                ArrayList <NameValuePair> httpParams = new  ArrayList <NameValuePair> ();
                httpParams.add(new BasicNameValuePair("DIR", cmddir));
                cmddir = URLEncodedUtils.format (httpParams, "UTF-8" );

                Set <Integer> dates = new HashSet <Integer>();                
                String files = FlashAirRequest
                            .getString("http://flashair/command.cgi?op=100&" + cmddir);
                String[] allFiles = files.split("([,\n])"); // split by newline or comma
                for(int i = 2; i < allFiles.length; i= i + 6) {
                  if(allFiles[i].contains(".")) {
                    if( (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpg"))
                    || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg"))
                    || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpe"))
                    || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".png"))){
                        // Image file
                        Integer date = Integer.parseInt(allFiles[i+3]);
                        dates.add(date);                          
                    }
                  }
                }                    

                // Get Title
                ArrayList<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
                for (Integer date : dates) {
                    Map<String, Object> entry = new HashMap<String, Object>();                    
                    String dataStr = FlashAirRequest.getString("http://flashair/" 
                                                        + dir + "/" + getDate(date, "") + ".txt");
                    String[] dataStrAry = dataStr.split("([\n])"); // split by newline or comma
                    entry.put("date", getDate(date, "/"));
                    if (dataStrAry.length >= 2) {
                        entry.put("title", dataStrAry[0]);
                    }                    
                    data.add(entry);
                }

               // Set the file list to a widget
                listAdapter = new SimpleAdapter(DateListActivity.this,
                        data,
                        R.layout.list_view_item,
                        new String[]{"date", "title"},
                        new int[]{R.id.textView1, R.id.textView2});
                return listAdapter;
            }
            @Override
            protected void onPostExecute(ListAdapter listAdapter) {
                listView = (ListView)findViewById(R.id.listView1);
                ColorDrawable divcolor = new ColorDrawable(Color.rgb(17, 19, 58));
                listView.setDivider(divcolor);
                listView.setDividerHeight(1);
                listView.setAdapter(listAdapter);
                listView.setOnItemClickListener(DateListActivity.this);                
            }
        }.execute(dir);
  • Lines 22-37:
    We get the date of each file.
  • Lines 40-51:
    We create a file path based on the date that we get, and then download the file.
    The tile is saved in the same folder as the image in the form of YYYYMMDD.txt. The title is saved on the top line and notes are on the second line.
    getDate() generates the date string(YYYYMMDD) from the date format(16-bit integer in decimal notation) which is returned by using op=100 with command.cgi.
  • Lines 54-58:
    We set the list_view_item.xml file that we already created as the layout and also set the date, title and notes.
    They are bound to the ListView in line 64.
  • Lines 63-68:
    We create a ListView and pass it the listAdapter we created earlier.

Setting the Item Click Listener

We will set the list behavior to be the same as it was in Android Tutorial 4: Displaying Image Thumbnails .
If the item clicked is the name of a date, the next screen will display the operation screen of that date.

DateListActivity.java (4)

    @Override
    public void onItemClick(AdapterView<?> l, View v, int position, long id) {
        Object item = l.getItemAtPosition(position); 
        if(item instanceof Map<?, ?>) {
            Map<String, Object> mapItem = (Map<String, Object>) item;
            Object selectDate = mapItem.get("date");
            // Next button to start new intent to allow day detail
            Intent memoEdit = new Intent(getBaseContext(), MemoEditActivity.class);     
            memoEdit.putExtra("date", selectDate.toString());
            memoEdit.putExtra("dir", directoryName);    
            DateListActivity.this.startActivity(memoEdit);        
        }
    }
  • Lines 4-6:
    We get the selected date from the ListView by using a Map object.

Setting Up the Redisplay Behaviour

We want the list to be updated when the screen is restarted (for example when the user hits the back button).

DateListActivity.java (5)

    @Override
    public void onRestart(){
        super.onRestart();
        listDirectory(directoryName);
    }

Getting the Date String (for setting the local file name)

The method which generates the date string(YYYYMMDD) from the 16-bit date format previously mentioned.

DateListActivity.java (6)

    public String getDate(Integer date, String sep) {
        return  String.format("%04d", ((date >> 9) & 0x1FF)+1980)+sep+
                String.format("%02d", (date >> 5)  & 0xF)+sep+
                String.format("%02d", date & 0x1F);
    }

Creating the Operation Screen

Initialization

We will start by declaring the views and changing the class declaration. We will also override the onCreate(Bundle savedInstanceState) function that initializes the Activity class. We want the list initialization and set a click listener for the Button.

MemoEditActivity.java (1)

public class MemoEditActivity extends Activity {
    TextView dateText;
    TextView currentDirText;
    Button backButton;
    Button saveButton;
    EditText titleField;
    EditText memoField;
    GridView gridView;
    String newTitle = "";
    String newMemo = "";
    String date;
    String rootDir = "DCIM";
    String directoryName; 
    ArrayList<String> fileNamelist;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memo_edit);

        Bundle extrasData = getIntent().getExtras();
        date = extrasData.getString("date");
        directoryName = extrasData.getString("dir");

        getWindow().setTitleColor(Color.rgb(65, 183, 216));
        // Set backButton
        backButton = (Button) findViewById(R.id.button1);
        backButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
        backButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MemoEditActivity.this.finish(); // Go back
            }
        });

        // Set saveButton
        saveButton = (Button) findViewById(R.id.button2);
        saveButton.getBackground().setColorFilter(Color.rgb(65, 183, 216), PorterDuff.Mode.SRC_IN);
        saveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getInput();
                saveNewTitleMemo();
            }
        });

        // Set titleField and memoField
        titleField = (EditText) findViewById(R.id.editTitle);
        titleField.setHintTextColor(Color.rgb(65, 183, 216));
        memoField = (EditText) findViewById(R.id.editMemo);
        memoField.setHintTextColor(Color.rgb(65, 183, 216));

        // Set dateText
        dateText = (TextView) findViewById(R.id.textView2);
        dateText.setText(date);

        // Get title and memo
        new AsyncTask<String, Void, ArrayList<String>>() {
            @Override
            protected ArrayList<String> doInBackground(String... params) {
                String dir = params[0];
                ArrayList<String> rtnAry = new ArrayList<String>();
                String files = FlashAirRequest.getString("http://flashair/" 
                                            + dir + "/" + date.replaceAll("/", "") + ".txt");
                String[] allFiles = files.split("([\n])"); // split by newline or comma
                if (allFiles.length >= 2) {
                    // File
                    rtnAry.add(allFiles[0]);
                    rtnAry.add(allFiles[1]);
                }
                return rtnAry;
            }

            @Override
            protected void onPostExecute(ArrayList<String> strary) {
                // Set the data to a TextView
                titleField = (EditText) findViewById(R.id.editTitle);
                memoField = (EditText) findViewById(R.id.editMemo);

                if (strary.size() > 0) {
                    titleField.setText(strary.get(0));
                    memoField.setText(strary.get(1));
                } else {
                    titleField.setText("");
                    memoField.setText("");
                }
            }
        }.execute(directoryName);

        // Set gridView
        gridView = (GridView) findViewById(R.id.gridView1);
        gridView.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View v,
                        int position, long id) {
                Object item = parent.getItemAtPosition(position); 
                if (item instanceof Map<?, ?>) {
                    Map<String, Object> mapItem = (Map<String, Object>) item;
                    Object downloadFile = mapItem.get("fname");
                    if ((downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpg"))
                    || (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg"))
                    || (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".jpe"))
                    || (downloadFile.toString().toLowerCase(Locale.getDefault()).endsWith(".png"))) 
                    {
                        // Image file, download using ImageViewActivity
                        Intent viewImageIntent = new Intent(MemoEditActivity.this, 
                                                                ImageViewActivity.class);
                        viewImageIntent.putExtra("downloadFile",downloadFile.toString());
                        viewImageIntent.putExtra("directoryName", directoryName);
                        MemoEditActivity.this.startActivity(viewImageIntent);
                    } // Not an image file, do nothing
                }
            }
        });
        listDirectory(directoryName);
    }
  • Lines 37-45:
    We set save button to run save processing when pressed.
  • Lines 58-88:
    We get the title and notes of selected date from the previous screen, and display them.
  • Lines 91-113:
    We download the image and display it.

Showing Thumbnails

This is the same method that we used earlier.
Except that we display thumbnails with GridView instead of ListView.

MemoEditActivity.java (2)

    public void listDirectory(String dir) {
        // Prepare command directory path
        currentDirText = (TextView)findViewById(R.id.textView1);
        currentDirText.setText(dir + "/");        

        final ProgressDialog waitDialog;        
        // Setting ProgressDialog
        waitDialog = new ProgressDialog(this);
        waitDialog.setMessage("Now downloading...");
        waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        waitDialog.show();          

        ArrayList<NameValuePair> httpParams = new ArrayList<NameValuePair>();
        httpParams.add(new BasicNameValuePair("DIR", dir));
        dir = URLEncodedUtils.format(httpParams, "UTF-8");
        // Fetch list of items in directory and display in a ListView
        new AsyncTask<String, Void, ListAdapter>() {
            @Override
            protected ListAdapter doInBackground(String... params) {
                String dir = params[0];
                ArrayList<String> fileNames = new ArrayList<String>();
                fileNamelist = new ArrayList<String>();
                String files = FlashAirRequest
                        .getString("http://flashair/command.cgi?op=100&" + dir);
                String[] allFiles = files.split("([,\n])"); // split by newline�@or comma
                for (int i = 2; i < allFiles.length; i = i + 6) {
                  if (allFiles[i].contains(".") && allFiles[i+3].contains(getDate16(date))) {
                    if( (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpg")) 
                    || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpeg"))
                    || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".jpe")) 
                    || (allFiles[i].toString().toLowerCase(Locale.getDefault()).endsWith(".png"))){
                        // Image file
                        fileNames.add(allFiles[i]);
                    }
                  }
                }

                // Get thumbnails
                ArrayList<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
                for (int i = 0; i < fileNames.size(); i++) {
                    String url = "";
                    url = "http://flashair/thumbnail.cgi?" + directoryName + "/" + fileNames.get(i);
                    Map<String, Object> entry = new HashMap<String, Object>();
                    Bitmap thumbnail = null;
                    BitmapDrawable drawnIcon = null;
                    if ((url.toLowerCase(Locale.getDefault()).endsWith(".jpg")) 
                        || (url.toLowerCase(Locale.getDefault()).endsWith(".jpeg"))) {
                        thumbnail = FlashAirRequest.getBitmap(url);
                        drawnIcon = new BitmapDrawable(getResources(), thumbnail);
                    }
                    if(thumbnail == null) {
                        entry.put("thmb", R.drawable.ic_launcher);
                    }
                    else {
                        entry.put("thmb", drawnIcon);
                    }
                    entry.put("fname", fileNames.get(i)); // Put file name onto the map
                    data.add(entry);                    
                }

                // Set the file list to a widget
                SimpleAdapter listAdapter = new SimpleAdapter(
                        MemoEditActivity.this, data, R.layout.grid_view_item,
                        new String[] { "thmb", "fname" }, 
                        new int[] {R.id.imageView1, android.R.id.text1 });
                listAdapter.setViewBinder(new CustomViewBinder());

                return listAdapter;
            }

            @Override
            protected void onPostExecute(ListAdapter adapter) {
                waitDialog.dismiss();
                gridView.setAdapter(adapter);
            }
        }.execute(dir);
    }
  • Lines 20-36:
    We get the file name for the target date. getDate16() generates the date string in a date format(16-bit integer in decimal notation) from the target date format(YYYY/MM/DD).
  • Lines 62-66:
    We set the grid_view_item.xml file as the layout and also set the thubmnail and file name. These are bound to the GridView in line 68.
  • Lines 72-75:
    Date, title and notes are bound to the view.

Creating ViewBinder

Here we'll create a custom ViewBinder class, which will be used to get thumbnails. GridView.

MemoEditActivity.java (3)

    class CustomViewBinder implements ViewBinder {
        @Override
        public boolean setViewValue(View view, Object obj, String text) {
            if((view instanceof ImageView) && (obj instanceof Drawable)) {
                ImageView imageView = (ImageView) view;
                BitmapDrawable thumbnail = (BitmapDrawable) obj;
                imageView.setImageDrawable((Drawable)thumbnail);
                return true;
            }
            return false;
        }
    }

Uploading

We will use the upload CGI command to save the title and notes.

  • Uploading follows these steps by using upload.cgi.
    1. Set UPLOAD=1 in the CONFIG file
    2. Restart the FlashAir
    3. Reconnect to the FlashAir
    4. Restrict the write ability of host devices using the WRITEPROTECT command
    5. Set the directory we want to upload to with the UPDIR command
    6. Set the file creation date using the FTIME command
    7. POST the file to upload.cgi!

Add upload(), which uses upload.cgi by POST request, to the FlashAirRequest.java file from Android Tutorial 3: Downloading Content.

FlashAirRequest.java

    static public String upload(String command, String filename, String saveString) {            
        String result = "";
        final String boundary = "========================";
        try {
            URL url = new URL(command);
            HttpURLConnection httpUrlCon = (HttpURLConnection)url.openConnection();
            httpUrlCon.setDoInput(true);
            httpUrlCon.setDoOutput(true);
            httpUrlCon.setUseCaches(false);
            httpUrlCon.setRequestMethod("POST");    
            httpUrlCon.setRequestProperty("Charset", "UTF-8");
            httpUrlCon.setRequestProperty("Content-Type", 
                                            "multipart/form-data;boundary="+ boundary);
            DataOutputStream ds = new DataOutputStream(httpUrlCon.getOutputStream());
            ds.writeBytes("--" + boundary + "\r\n");
            ds.writeBytes("Content-Disposition: form-data; name=\"upload.cgi\";
                            filename=\"" + filename +"\""+"\r\n");
            ds.writeBytes( "\r\n" );               
            ds.write(saveString.getBytes("UTF-8"));               
            ds.writeBytes( "\r\n" );     
            ds.writeBytes("--" + boundary + "--" + "\r\n");
            ds.flush();
            ds.close();
            if(httpUrlCon.getResponseCode() == HttpURLConnection.HTTP_OK){
                StringBuffer sb = new StringBuffer();
                InputStream is = httpUrlCon.getInputStream();    
                byte[] data = new byte[1024];
                int leng = -1;
                while((leng = is.read(data)) != -1) {
                    sb.append(new String(data, 0, leng));
                }                
                result = sb.toString();    
            }
        } catch (MalformedURLException e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        } catch (IOException e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        }
        return result;                        
    }
  • Lines 12-13:
    We set the multipartform-data format.

MemoEditActivity.java (4)

    public void saveNewTitleMemo() {  
        new AsyncTask<String, Void, String>(){
            @Override
            protected String doInBackground(String... params) {  
                String parameter = "?WRITEPROTECT=ON&UPDIR=/" + rootDir;    
                parameter = parameter + "&FTIME=" + getDateTime16();
                String filename = date.replaceAll("/", "") + ".txt";
                String rtnStr = "";                    
                rtnStr = FlashAirRequest.getString(params[0] + parameter);
                if(rtnStr.toUpperCase(Locale.getDefault()).equals("SUCCESS")) {
                    rtnStr = FlashAirRequest
                            .upload( params[0], filename, newTitle + "\n" + newMemo);
                }
                return     rtnStr;
            }    
            @Override
            protected void onPostExecute(String result) {
                if(result.toUpperCase(Locale.getDefault()).indexOf("SUCCESS") >= 0) {
                    Toast.makeText(MemoEditActivity.this, "Save Completed.", 
                                    Toast.LENGTH_LONG).show();
                }                    
            }
        }.execute("http://flashair/upload.cgi");
    }
  • Lines 5-9:
    We complete steps 4 to 6.
    getDateTime16() generates a date string from the current date to indicate the creation date of the file we're uploading. It creates a date and a time as 16-bit hexadecimal values, and concatinates them.
  • Lines 11-12:
    We upload the title and notes using upload().

Getting a Date String for a file's creation date

The method generates a date string from the current date to indicate the creation date of the file we're uploading, as previously mentioned.

MemoEditActivity.java (5)

    public String getDateTime16() {

        Calendar calendar = Calendar.getInstance();
        int year = (calendar.get(Calendar.YEAR) - 1980) << 9;
        int month = (calendar.get(Calendar.MONTH) + 1) << 5;
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        int hours = calendar.get(Calendar.HOUR_OF_DAY) << 11;
        int minites = calendar.get(Calendar.MINUTE) << 5;
        int seconds = calendar.get(Calendar.SECOND) / 2;
        String rtnStr = "0x" +  Integer.toHexString(year + month + day) 
                                    + Integer.toHexString(hours + minites + seconds);

        return rtnStr;
    }

Converting Our Date to the 16-bit Format

The method generates a date string in the 16-bit format, from the target date format(YYYY/MM/DD).

MemoEditActivity.java (6)

    public String getDate16(String date) {
        String rtnStr = "";        
        try{
            int year = (Integer.parseInt(date.substring(0,4)) - 1980) << 9;        
            int month = (Integer.parseInt(date.substring(5,7))) << 5;        
            int day = Integer.parseInt(date.substring(8,10));        
            rtnStr = String.valueOf(year + month + day); 
        }
        catch(Exception e) {
            Log.e("ERROR", "ERROR: " + e.toString());
            e.printStackTrace();
        }                  
         return rtnStr;
    }

Result

Let's set a title and save some notes.

This image shows the result
This image shows the result

Enter the title and note and tap the save button.

This image shows the result

When you return to the previous screen, you'll see your changes have been saved!

This image shows the result

Sample Code

android_tutorial_07.zip (533KB)

All sample code on this page is licensed under BSD 2-Clause License